'use strict';
angular.module('csField', ['jcs-autoValidate'])
.factory('$csBind', ['$parse', '$interpolate', function ($parse, $interpolate) {
    return function (scope, attrs, destination, bindScope) {
        var parseIsolateBindings = function (scope,isController) {
            var LOCAL_REGEXP = /^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/;

            var bindings = {};

            angular.forEach(scope, function (definition, scopeName) {
                var match = definition.match(LOCAL_REGEXP);

                if (!match) {
                    throw $compileMinErr('iscp',
                        "Invalid {3} for directive '{0}'." +
                        " Definition: {... {1}: '{2}' ...}",
                        'csbind', scopeName, definition,
                        (isController ? "controller bindings definition" :
                        "isolate scope definition"));
                }

                bindings[scopeName] = {
                    mode: match[1][0],
                    collection: match[2] === '*',
                    optional: match[3] === '?',
                    attrName: match[4] || scopeName
                };
            });

            return bindings;
        }
       var bindings = parseIsolateBindings(bindScope, false);
                    
        var removeWatchCollection = [];
        angular.forEach(bindings, function (definition, scopeName) {
            var attrName = definition.attrName,
            optional = definition.optional,
            mode = definition.mode, // @, =, or &
            lastValue,
            parentGet, parentSet, compare, removeWatch;

            switch (mode) {

                case '@':
                    if (!optional && !hasOwnProperty.call(attrs, attrName)) {
                        destination[scopeName] = attrs[attrName] = void 0;
                    }
                    attrs.$observe(attrName, function (value) {
                        if (angular.isString(value)) {
                            destination[scopeName] = value;
                        }
                    });
                    attrs.$$observers[attrName].$$scope = scope;
                    lastValue = attrs[attrName];
                    if (angular.isString(lastValue)) {
                        // If the attribute has been provided then we trigger an interpolation to ensure
                        // the value is there for use in the link fn
                        destination[scopeName] = $interpolate(lastValue)(scope);
                    } else if (isBoolean(lastValue)) {
                        // If the attributes is one of the BOOLEAN_ATTR then Angular will have converted
                        // the value to boolean rather than a string, so we special case this situation
                        destination[scopeName] = lastValue;
                    }
                    break;

                case '=':
                    if (!hasOwnProperty.call(attrs, attrName)) {
                        if (optional) break;
                        attrs[attrName] = void 0;
                    }
                    if (optional && !attrs[attrName]) break;

                    parentGet = $parse(attrs[attrName]);
                    if (parentGet.literal) {
                        compare = angular.equals;
                    } else {
                        compare = function (a, b) { return a === b || (a !== a && b !== b); };
                    }
                    parentSet = parentGet.assign || function () {
                        // reset the change, or we will throw this exception on every $digest
                        lastValue = destination[scopeName] = parentGet(scope);
                        throw $compileMinErr('nonassign',
                            "Expression '{0}' in attribute '{1}' used with directive '{2}' is non-assignable!",
                            attrs[attrName], attrName, 'field');
                    };
                    lastValue = destination[scopeName] = parentGet(scope);
                    var parentValueWatch = function parentValueWatch(parentValue) {
                        if (!compare(parentValue, destination[scopeName])) {
                            // we are out of sync and need to copy
                            if (!compare(parentValue, lastValue)) {
                                // parent changed and it has precedence
                                destination[scopeName] = parentValue;
                            } else {
                                // if the parent can be assigned then do so
                                parentSet(scope, parentValue = destination[scopeName]);
                            }
                        }
                        return lastValue = parentValue;
                    };
                    parentValueWatch.$stateful = true;
                    if (definition.collection) {
                        removeWatch = scope.$watchCollection(attrs[attrName], parentValueWatch);
                    } else {
                        removeWatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal);
                    }
                    removeWatchCollection.push(removeWatch);
                    break;

                case '<':
                    if (!hasOwnProperty.call(attrs, attrName)) {
                        if (optional) break;
                        attrs[attrName] = void 0;
                    }
                    if (optional && !attrs[attrName]) break;

                    parentGet = $parse(attrs[attrName]);

                    destination[scopeName] = parentGet(scope);

                    removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newParentValue) {
                        destination[scopeName] = newParentValue;
                    }, parentGet.literal);

                    removeWatchCollection.push(removeWatch);
                    break;

                case '&':
                    // Don't assign Object.prototype method to scope
                    parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop;

                    // Don't assign noop to destination if expression is not valid
                    if (parentGet === noop && optional) break;

                    destination[scopeName] = function (locals) {
                        return parentGet(scope, locals);
                    };
                    break;
            }
        });

        var removeScopeBindingWatches = removeWatchCollection.length && function removeWatches() {
            for (var i = 0, ii = removeWatchCollection.length; i < ii; ++i) {
                removeWatchCollection[i]();
            }
        };
        if (removeScopeBindingWatches) {
            destination.$on('$destroy', removeScopeBindingWatches);
        }
    }
}])
.factory('fieldSettings', ['$compile', '$http', '$templateCache', '$location', '$q', '$state', '$injector', '$parse', function ($compile, $http, $templateCache, $location, $q, $state, $injector, $parse) {
    var global = {
        device: 'common',
        promises: [],
        loadTemplate: function (url) {
            var pro = $http.get(url).then(function (response) {
                $compile(response.data);
                return response;
            });

            global.promises.push(pro);
            return pro;
        },
        compileTemplate: function (definition, $scope, element, defaultTemplateUrl, replace) {
            var deferred = $q.defer();
            var template;
            if (definition.template) { // 直接设定模板内容
                template = definition.template;
                //element.html(template).show();
                //$compile(element.contents())($scope);
                var e = $compile(template)($scope);
                element.replaceWith(e);
                deferred.resolve();
            } else { // 指定模板的url
                var templateUrl = definition.templateUrl || defaultTemplateUrl;
                $q.all(global.promises).then(function () {
                    template = $templateCache.get(templateUrl);
                    if (template) {
                        //element.html(template).show();
                        //$compile(element.contents())($scope);
                        var e = $compile(template)($scope);
                        if (replace) {
                            element.replaceWith(e);
                        } else {
                            element.html(e);
                        }
                    } else {
                        throw 'can not find template : ' + templateUrl;
                    }
                    deferred.resolve();
                });
            }
            return deferred.promise;
        }
    };
    return global;
}])
.directive('fieldLabel', ['$rootScope', 'fieldSettings', '$templateCache', '$location', '$parse', function ($rootScope, fieldSettings, $templateCache, $location, $parse) {
    return {
        restrict: 'E',
        link: function ($scope, element, attrs) {
            if (element.css('display') == 'inline') {
                element.css('display', 'block');
            }
            

            var schema;
            if (attrs.schema) {
                schema = $parse(attrs.schema)($scope);
                angular.extend(schema, attrs);
            } else {
                schema = attrs;
            }
            var render = function () {
                var newScope = $scope.$new();
                newScope.schema = schema;
                fieldSettings.compileTemplate(schema, newScope, element, 'fields/label/label.html', false).then(function () {
                });
            };

            render();
        }

    };
}])
.directive('fieldControl', ['$rootScope', 'fieldSettings', '$templateCache', '$location', '$parse', '$csBind', '$q', '$compile', function ($rootScope, fieldSettings, $templateCache, $location, $parse, $csBind, $q, $compile) {
    return {
        restrict: 'E',
        require: '?ngModel',
        scope: true,
        link: function ($scope, element, attrs, ctrl) {
            var destroyCollection = [];
            var oldValue = ctrl.$viewValue;

            if (element.css('display') == 'inline') {
                element.css('display', 'block');
            }

            if (!attrs.registerCustomFormControl) {
                element.attr("register-custom-form-control", "");
            }
            if (!angular.isDefined(attrs.enableValidate)) {
                element.attr("enable-validate", "true");
            }
            element.addClass("form-group");
            if (!attrs.layout) {
                attrs.layout = 'default';
            }

            //var defaultLayouts = {
            //    'default': { label: 'col-md-4', control: 'col-md-6', field: 'col-md-12' },
            //    '2col': { label: 'col-md-4', control: 'col-md-8', field: 'col-md-6' },
            //    '1col': { label: 'col-md-2', control: 'col-md-10', field: 'col-md-12' },
            //    'HiddenLabel': { label: 'hidden', control: '', field: '' }
            //}

            //if (!attrs.class && defaultLayouts[attrs.layout].field) {
            //    element.addClass(defaultLayouts[attrs.layout].field);
            //}

            var schema;
            if (attrs.schema) {
                schema = $parse(attrs.schema)($scope);
                angular.extend(schema, attrs);
            } else {
                schema = attrs;
            }
            var render = function () {
                //var newScope = $scope.$new();
                var newScope = $scope;
                newScope.schema = schema;

                //newScope.ngModelCtrl = ctrl;
                newScope.value = function () {
                    if (arguments.length > 0) {
                        ctrl.$setViewValue(arguments[0]);
                        oldValue = ctrl.$viewValue;
                    }
                    return ctrl.$viewValue;
                };

                //newScope.labelTemplate = "fields/label/label.html";
                newScope.controlTemplate = "fields/control/" + schema.type + ".html";
                //newScope.defaultClass = defaultLayouts[attrs.layout];

                var dataBind = function (bindStr) {
                    var bindScopeDefinitions = $parse(bindStr)(newScope);
                    if (!angular.isDefined(bindScopeDefinitions.ngModel)) {
                        bindScopeDefinitions.ngModel = '=';
                    }
                    $csBind($scope, attrs, newScope, bindScopeDefinitions);

                    var unwatch = newScope.$watch('ngModel', function (value) {
                        if (oldValue != value) {
                            oldValue = value;
                            ctrl.$setViewValue(value);
                        }
                    });
                    destroyCollection.push(unwatch);
                };

                var compileTemplate = function (replace) {
                    var deferred = $q.defer();
                    $q.all(fieldSettings.promises).then(function () {

                        //var labelHtml = $templateCache.get(newScope.labelTemplate);
                        var controlHtml = $templateCache.get(newScope.controlTemplate);
                        var bindElements = $('<div>' + controlHtml + '</div>').find('span[data-cs-binds]');
                        if (bindElements && bindElements.length > 0) {
                            var bindStr = bindElements.attr('data-cs-binds');
                            dataBind(bindStr);
                        } else {
                            dataBind('{}');
                        }
                        //var template = '<div class="control-label {{schema.labelClass || defaultClass.label}}">' + labelHtml + '</div>'
                        //            + '<div class="{{schema.controlClass || defaultClass.control}}">'
                        //                + '<div>' + controlHtml + '</div>'
                        //                + '<div class="auto-validate-error-container"></div>'
                        //            + '</div>';
                        var template = controlHtml;
                        var e = $compile(template)($scope);
                        if (replace) {
                            element.replaceWith(e);
                        } else {
                            element.html(e);
                        }
                        deferred.resolve();
                    });

                };

                compileTemplate(false);

                $scope.$on('$destroy', function () {
                    for (var i = 0, ii = destroyCollection.length; i < ii; ++i) {
                        destroyCollection[i]();
                    }
                });
            };

            render();
        }
    };
}])
.directive('field', ['$rootScope', 'fieldSettings', '$templateCache', '$location', '$parse', '$csBind', '$q', '$compile', function ($rootScope, fieldSettings, $templateCache, $location, $parse, $csBind, $q, $compile) {
    return {
        restrict: 'EA',
        require: '?ngModel',
        scope:true,
        link: function ($scope, element, attrs, ctrl) {
            var destroyCollection = [];
            var oldValue = ctrl.$viewValue;
            
            if (element.css('display') == 'inline') {
                element.css('display', 'block');
            }

            if (!attrs.registerCustomFormControl) {
                element.attr("register-custom-form-control", "");
            }
            if (!angular.isDefined(attrs.enableValidate)) {
                element.attr("enable-validate", "true");
            }
            element.addClass("form-group");
            if (!attrs.layout) {
                attrs.layout = 'default';
            }

            var defaultLayouts = {
                'default': { label: 'col-md-4', control: 'col-md-6', field: 'col-md-12' },
                '3col': { label: 'col-md-6', control: 'col-md-6', field: 'col-md-4' },
                '3-2col': { label: 'col-md-3', control: 'col-md-9', field: 'col-md-8' },
                '2col': { label: 'col-md-4', control: 'col-md-8', field: 'col-md-6' },
                '1col': { label: 'col-md-2', control: 'col-md-10', field: 'col-md-12' },
                'HiddenLabel': { label: 'hidden', control: '', field: '' }
            }

            if (!attrs.class && defaultLayouts[attrs.layout].field) {
                element.addClass(defaultLayouts[attrs.layout].field);
            }

            var schema;
            if (attrs.schema) {
                schema = $parse(attrs.schema)($scope);
                angular.extend(schema, attrs);
            } else {
                schema = attrs;
            }
            var render = function () {
                //var newScope = $scope.$new();
                var newScope = $scope;
                newScope.schema = schema;
                
                //newScope.ngModelCtrl = ctrl;
                newScope.value = function () {
                    if (arguments.length > 0) {
                        console.log("filed set value:" + arguments[0] + " | type:" + schema.type + " | name:" +schema.name);
                        ctrl.$setViewValue(arguments[0]);
                        oldValue = ctrl.$viewValue;
                    }
                    return ctrl.$viewValue;
                };

                newScope.labelTemplate = "fields/label/label.html";
                newScope.controlTemplate = "fields/control/" + schema.type + ".html";
                newScope.defaultClass = defaultLayouts[attrs.layout];
                
                var dataBind = function (bindStr) {
                    var bindScopeDefinitions = $parse(bindStr)(newScope);
                    if (!angular.isDefined(bindScopeDefinitions.ngModel)) {
                        bindScopeDefinitions.ngModel = '=';
                    }
                    $csBind($scope, attrs, newScope, bindScopeDefinitions);

                    var unwatch = newScope.$watch('ngModel', function (value) {
                        if (oldValue != value) {
                            oldValue = value;
                            ctrl.$setViewValue(value);
                            if (attrs.ngChange) {
                                $parse(attrs.ngChange)(newScope);
                            }
                        }
                    });
                    destroyCollection.push(unwatch);
                };

                var compileTemplate = function (replace) {
                    var deferred = $q.defer();
                    $q.all(fieldSettings.promises).then(function () {

                        var labelHtml = $templateCache.get(newScope.labelTemplate);
                        var controlHtml = $templateCache.get(newScope.controlTemplate);
                        var bindElements = $('<div>'+controlHtml+'</div>').find('span[data-cs-binds]');
                        if (bindElements && bindElements.length > 0) {
                            var bindStr = bindElements.attr('data-cs-binds');
                            dataBind(bindStr);
                        } else {
                            dataBind('{}');
                        }
                        var template = '<div class="control-label {{schema.labelClass || defaultClass.label}}">' + labelHtml + '</div>'
                                    + '<div class="{{schema.controlClass || defaultClass.control}}">'
                                        + '<div>' + controlHtml + '</div>'
                                        + '<div class="auto-validate-error-container"></div>'
                                    + '</div>';

                        var e = $compile(template)($scope);
                        if (replace) {
                            element.replaceWith(e);
                        } else {
                            element.html(e);
                        }
                        deferred.resolve();
                    });

                };

                compileTemplate(false);

                $scope.$on('$destroy', function () {
                    for (var i = 0, ii = destroyCollection.length; i < ii; ++i) {
                        destroyCollection[i]();
                    }
                });
            };

            render();
        }
    };
}])
.directive('attr', ['$parse', function ($parse) {
    return {
        restrict: 'A',
        link: function(scope, element, attr) {
            var oldVal;
            var attrsVals = $parse(attr.attr)(scope);
            
            //function watchAction(newVal) {
            //    if (newVal != oldVal) {
            //        updateAttrs(newVal)
            //    }
            //    oldVal = _.clone(newVal)
            //}


            function updateAttrs(newAttrs) {
                addAttributes(getAttributesToAdd(newAttrs));
            }

            function getAttributesToAdd(newAttrs) {
                var attrsToAdd = {};

                if (angular.isString(newAttrs)) {
                    angular.forEach(newAttrs.split(), function(attrName) {
                        attrsToAdd[attrName] = '';
                    });
                }
                else if (angular.isArray(newAttrs)) {
                    angular.forEach(newAttrs, function(attrName) {
                        attrsToAdd[attrName] = '';
                    });
                }
                else if (angular.isObject(newAttrs)) {
                    angular.forEach(newAttrs, function(attrValue, attrName) {
                        if (typeof attrValue === "boolean") {
                            if (attrValue) {
                                attrsToAdd[attrName] = '';
                            }
                        }
                        else {
                            attrsToAdd[attrName] = attrValue;
                        }
                    });
                }
                return attrsToAdd;
            }

            function addAttributes(attrsToAdd) {
                angular.forEach(attrsToAdd, function(attrValue, attrName) {
                    element.attr(attrName, attrValue);
                });
            }

            updateAttrs(attrsVals);
        }
    }
}])
.run(['$http', '$templateCache', function ($http, $templateCache) {
    
}]);